#pragma once

#include <iostream>
#include <vector>
#include <boost/thread/once.hpp>
#include <boost/thread/mutex.hpp>
#include <windows.h>

#include "SafeList.h"
#include "Tls.h"
#include "Condition.h"
#include "BasicEvent.h"

namespace events {

/**
 * This class is used to dispatch events, and to save them to output file
 */
class PostOffice
{
	typedef platform::SafeList<BasicEvent> thread_events;
	typedef platform::Tls<thread_events> all_events;

	/**
	 * @return list of events associated with this thread
	 */
	inline thread_events & getThreadList(void) {
		thread_events * pe = _events.get();
		if (pe == NULL) {
			pe = new thread_events();
			_events.set(pe);
		}
		return *pe;
	}

	/** is called by TLS once the thread terminates */
	static void ThreadExits(thread_events *list);

public:
	virtual ~PostOffice(void) {}

	/** @return singleton instance */
	static inline PostOffice & GetInstance(void);

	/** sends event to output */
	inline void send(BasicEvent *be) { getThreadList().push_back(be); }

	/** starts events' logger (in newly created thread) */
	void startLoggerInThisThread(std::ostream &os, DWORD startTime);

	/** stops logger thread */
	inline void stopLogger(void) { _stopped = true; _condStop.set(); _condStopDone.wait(); }

private:
	/** writes ABPA header to file */
	void writeFileHeader(std::ostream &os, DWORD startTime);

	all_events _events;
	volatile bool _stopped;
	platform::Condition _condStop;
	platform::Condition _condStopDone;

	// managing singleton instance
	static PostOffice * _instance;
	static boost::mutex _inst_mutex;
public:
	static boost::once_flag _instance_flag;
	static inline void CreateInstance(void) { _instance = new PostOffice(); }

	inline PostOffice(void) : _events(ThreadExits), _stopped(false)/*, _out(NULL)*/ {}

private:
	/**
	 * Helper class, builds a list (array) of HANDLEs from
	 * all threads' event lists, and later waites until
	 * one of the list gets signaled
	 */
	class MultilistWaiter {
		typedef std::vector<HANDLE> handle_vector;
		handle_vector _handles;
		unsigned int _counter;

	public:
		inline MultilistWaiter(void) : _counter(0) {}
		inline void reset(void) { _counter = 0; }

		inline void addHandle(HANDLE h) {
			_counter++;
			if (_counter < _handles.size())
				_handles[_counter-1] = h;
			else
				_handles.push_back(h);
		}

		inline void operator()(thread_events *pl) {
			addHandle(pl->getWaitEvent());
		}

		inline DWORD wait(unsigned int msec, HANDLE h = NULL) {
			if (h != NULL) {
				if (_counter < MAXIMUM_WAIT_OBJECTS)
					addHandle(h);
				else
					// always wait for "h" handle
					_handles[0] = h;
			}

			bool bTooMuch = false;
			unsigned int waitCounter = _counter;
			if (waitCounter > MAXIMUM_WAIT_OBJECTS) {
				bTooMuch = true;
				waitCounter = MAXIMUM_WAIT_OBJECTS;
			}

			if (_handles.size() == 0) {
				::Sleep(msec);
				return WAIT_TIMEOUT;
			}

			// this function can wait for 64 objects at most, in case
			// that we have more threads to wait for, we will decrease
			// the timeout - so we will decrease the timeout
			return ::WaitForMultipleObjects((DWORD) waitCounter, &(_handles[0]), false, bTooMuch ? msec / 5 : msec);
		}
	};


	/**
	 * Is used to sent events to output when ready
	 */
	class EventsWriter {
		std::ostream & _out;
		unsigned long _total_events;

	public:
		inline EventsWriter(std::ostream &os) : _out(os), _total_events(0) {}

		inline void operator()(thread_events *pl) {
			BasicEvent *be;
			while ((be = pl->pop_front()) != NULL) {
				_out << (*be);
				_total_events++;
				delete be;
			}
		}

		inline unsigned long getNumOfEvents(void) const { return _total_events; }
	};


	/**
	 * Is used to collect all remaining events
	 * when exitting PostOffice thread
	 */
	class OrphanEventsCollector {
		thread_events::OrphanElementsHandler *_handler;

	public:
		inline OrphanEventsCollector(thread_events::OrphanElementsHandler *handler) : _handler(handler) {}
		inline void operator()(thread_events *pl) { pl->flushOrphans(_handler);	}
	};

	/**
	 * Is used to register orphan events handler
	 * NOTE: similar to previous class - merge to single one?
	 */
	class OrphanEventsRegistry {
		thread_events::OrphanElementsHandler *_handler;

	public:
		inline OrphanEventsRegistry(thread_events::OrphanElementsHandler *handler) : _handler(handler) {}
		inline void operator()(thread_events *pl) { pl->setOrphanElementsHandler(_handler);	}
	};
};


inline PostOffice & PostOffice::GetInstance(void) {
	boost::call_once(CreateInstance, _instance_flag);
/*	if (_instance==NULL) {
		///boost::detail::thread::scoped_lock l(_inst_mutex);

		::EnterCriticalSection(&_cs);
		if (_instance==NULL)
			_instance = new PostOffice();
		::LeaveCriticalSection(&_cs);

	}*/
	return *_instance;
}


}  // end namespace
